Building plug-in tools for SICK Nova

SICK AppSpace apps built on SICK Nova can be extended with new tools developed as Nova plug-ins. This page describes how to use the plug-in API to develop tools for SICK Nova releases with API version 2.9.0.

For the detailed API reference, see Tool API 2.9.0.

Note

The tool plug-in API may be subject to change and complete forward compatibility with future releases is not guaranteed.

Requirements

Overview

Plug-in tools are implemented as individual SICK AppSpace apps. Communication between the SICK Nova SensorApp, the plug-in tool and its user interface is abstracted by the tool plug-in API, which is made available to the plug-in tool at run-time.

There is a collection of sample tools available on SICK AppPool. The sample tools are intended as examples on how to use the tool API. The Sample Blob Counter-tool will serve as reference in this document.

For viewing the source code of the sample tools:

  1. Deploy one or several Nova Sample Tools on the device (or emulator) in SICK AppManager.

  2. Open SICK AppStudio and connect to the device.

  3. Transfer the sample apps to your working directory in AppStudio (the apps are shown under the Device tab).

  4. Access the script component of the app to view the source code.

When creating your own tool, you can generate a template for it using the Tool generator.

App content

Hint

“MyTool” in this documentation is a placeholder for the app name of your tool.

../_images/overview-app-content.png

App content in AppStudio

The tool plug-in API is enabled by the module Nova.Tool, which should be loaded by the tool script file. This is described in MyTool file.

Tool plug-in apps have the following components:

  • resources: includes icon, language file and help text for user interface. See Resources.

  • scripts: include the source code of the plug-in tool. See Scripts.

  • project file: Specifies App metadata. See Project file.

Project file

The project file, at "MyTool/project.mf.xml" is automatically generated when an app is created in AppStudio, but must be adapted for Nova.

The project-file contains meta-information about the app, such as its protection levels, version, entry point and its served functions and events. For your plug-in tool to work, the "project.mf.xml" must have specific content which is similar for all tools.

Hint

When using the Tool generator, the project.mf.xml will be created with the correct information

This is a template for the project-file for a Nova tool:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<manifest>
    <application name="MyTool">
        <crown name="MyTool">
            <desc></desc>
            <serves>
                <event name="ToolEvent">
                    <desc></desc>
                    <param name="arg" type="string" desc=""/>
                </event>
            <function name="command">
                <desc></desc>
                <param name="cmd" type="string" desc=""/>
                <param name="arg" type="auto" multiplicity="?" desc=""/>
                <return name="result" type="auto" multiplicity="?" desc=""/>
            </function>
            <function name="ui">
                <desc></desc>
                <param name="cmd" type="string" desc=""/>
                <param name="arg" type="auto" desc=""/>
                <return name="result" type="string" desc=""/>
            </function>
            </serves>
        </crown>
        <meta key="author">TemplateAuthor</meta>
        <meta key="version">0.0.0</meta>
        <meta key="priority">low</meta>
        <meta key="read-protected">false</meta>
        <meta key="copy-protected">false</meta>
        <meta key="LuaLoadAllEngineAPI">true</meta>
        <entry path="scripts" default="Bootstrap.lua"/>
    </application>
</manifest>

Follow these steps to get a correct project file for your tool (or use the Tool generator):

  1. Open the “MyTool/project.mf.xml” created by AppStudio in a text editor (notepad, Visual Studio Code, etc.)

  2. Copy the content from the <meta key="author">...</meta> text and paste it somewhere so you can retrieve it later

  3. Replace the “project.mf.xml” with the template content above

  4. Replace all instances of “MyTool” in the template with the name of your app.

  5. Replace “TemplateAuthor” with the original author from step 2.

Note

Replacing the project file with the template above will set read protection and copy protection to false. Make sure to set protection levels according to your preferences after replacing the project file.

Note

Served events and functions are identical for all tools and do not need to be modified by the tool developer.

Resources

Resources include help text, user interface labels and tool icon, which make your tool easier to understand and to use.

Help Text resources

The help text is a description of the tool and its parameters and results which is accessible directly in the user interface. Multiple versions of the help text for a tool can be provided, one for each language supported by SICK Nova.

If a help text-file for the chosen language is provided, the help text will be accessible in the tool panel in the user interface as in this image:

../_images/overview-help-button.png

To add help text add a folder “resources/help/” with help text files with the correct names according to this table:

Language

Help text file

English

en.html

French

fr.html

German

de.html

Italian

it.html

Chinese

zh.html

Spanish

es.html

Japanese

ja.html

Korean

ko.html

Hint

When using the Tool generator, a blank English help text-file "resources/help/en.html" will be added automatically. This file can be copied and modified for each language you wish to support.

Language resource files

The SICK Nova user interface can be viewed in different languages. Plug-in tools can use resource files for translations and to specify readable names for tool features (like parameters). See Localization for details.

Hint

The Tool generator creates the English user interface labels file "resources/lang/en.json". The translations should be filled in by the tool developer.

Icon resource file

Add an icon to your tool by adding an image file in png or svg format to resources/, with the same name as your tool. The icon size should be 48x48 pixels with transparent background. The Tool generator generates unique example icons for tools.

Scripts

The scripts/ folder for a plug-in should have the files described in this section.

Bootstrap file

The file “scripts/Bootstrap.lua” is used for the start-up procedure of SICK Nova and is identical for all tool apps.

It should have the following content:

if _G["NovaMain"] == nil then
  _G["NovaMain"] = require("API.NovaMain")
end

_G["Script"].register("NovaMain.InitApps", function()
  load(_G["NovaMain"].bootstrap(), "bootstrap", "t")() --luacheck:ignore 113
  _G["_nova_bootstrap_run_app"](_APPNAME, 2)
end)

Hint

The Tool generator creates a bootstrap file with the correct content.

Note

The “Bootstrap.lua” file should be the the main file of the app:

../_images/overview-bootstrap-main.png

Setting “Bootstrap.lua” as the main file.

This is specified by <entry path="scripts" default="Bootstrap.lua"/> in the Project file.

MyTool file

Add your own tool file “scripts/MyTool.lua”. The name of this file should be the same as the name of the app, and with the extension “.lua”. All mentioned script content in this documentation should go into this file.

Hint

Using the Tool generator, a template for “scripts/MyTool.lua” is added automatically.

Tool registration

Tools register themselves to provide the SICK Nova SensorApp with the necessary information about them.

Add the following line at the top of “MyTool.lua” to include the Nova.Tool module:

local Tool = require("Nova.Tool")

Register the tool by calling Tool.register with the following structure:

local _MyTool = Tool.register{
    name="My tool",
    category="Analysis",
    regions=true,
    hasOverlays=true,
    parameters={},
    results={},
    thresholds={},
}

Note

The call to Tool.register is made with curly brackets: {}.

Here are some commonly used registration fields for Tool.register. See Tool Registration Arguments for the full list.

Registration field

Description

Type

Required

name

A label for the name of this tool for the user interface (see Localization)

string

No, defaults to App name

category

The tool category. This should be “Analysis” as plug-in support is not established for other tool types.

string

Yes

regions

Defines if the tool supports regions, and if so which types. Setting this to true will allow an unlimited number of 2D regions of any shapes. When true, or with a regions definition that is not limited to one region of a fixed type, the tool will have a UI control for specifying regions to operate on.

bool or regions definition

No, defaults to false

hasOverlays

Allow the tool to produce visualization overlays shown in the viewer.

bool

No, defaults to false

parameters

The parameters that control the tool. Described in section Parameters.

Parameter list table

No

results

The results produced by the tool. Described in section Results and thresholds.

Result list table

No

thresholds

Thresholds for results with user-configurable pass/fail ranges. Described in section Results and thresholds.

Threshold list table

No

The returned item from Tool.register is the custom tool class for the tool.

Parameters

Parameters control the tool execution. Depending on the type of parameter, a suitable control is included in the user interface settings pane of the tool to allow for configuration by the user.

Parameters are instance-specific. This means that if you add two instances of a tool in the user interface, changing a parameter for one of them will not change the corresponding parameter of the other instance.

Parameter values are saved in exported configurations so that tools are reconstructed with the same values when a configuration is imported.

A parameter is added as a dictionary-like Lua-table. The possible entries are given below:

Entry

Required

Entry type

name

Yes

string

type

Yes

string, see Parameter types

default

Depends on type

Depends on type

range

Depends on type

Depends on type

ui

Depends on type

Depends on type

values

Depends on type

Depends on type

optional

No

Table, see Optional parameters

See also:

  • Parameter types for the supported values for the “type” entry

  • Parameter groups for grouping parameters within a collapsible section in the user interface

  • Optional parameters for parameters that have an enable/disable toggle controlling if they have an effect

Parameter section example

Below is an example of a parameter section and the corresponding appearance in the user interface:

parameters={
    {name="IntensityThreshold", type="intensity8range", default={0, 200}},
    {name="PostProcess", type="bool", default=false},
    {name="Filter", type="enum", values={"No", "Gaussian", "Median"}, default="No"},
    {name="Iterations", type="int", range={2, 10}, ui={style="slider"}}
../_images/overview-parameter-section.png

See the sample tools and Parameter types for more examples of how parameters are specified.

Results and thresholds

For each execution step, tools produce results. Like parameters, the result values are instance specific and different results can have different types. The results of the tool can also be accessed by other tools and communicated to external devices. Certain results can also be visualized with overlays in the image view using the presentation API described in Presentation

Results are declared in the results-section of Tool.register. A result entry has the following fields:

Field

Required

Description

name

Yes

The label for the result

type

Yes

The data type for the result

ui

No

Options for user-interface representation

Values for all results specified in the result-section of Tool.register should be set in the execute-function using output:add, see SampleBlobCounter execute.

Each tool must have a Pass result. This is of type bool and can be interpreted as the overall result of the tool — a combination of the other results. To allow for this, numerical results can have a corresponding threshold that returns true if the result is within the specified interval.

Note

The threshold for a result must have the same name as the result.

Below is an example of a result and a corresponding threshold section. The result of Pass for this example is written to depend on whether the result Score is within its corresponding thresholds.

results={
    {name="Score", type="float"},
    {name="Pass", type="bool"}},
thresholds={
    {name="Score", type="percentrange", default={80.0, 100.0}}

The images below show the user interface appearance in the two cases of "Pass" = true and "Pass" = false.

../_images/overview-result-thresh-pass.png ../_images/overview-result-thresh-fail.png

It is not necessary to have a corresponding threshold for a result. Without a threshold the result will be shown in the user interface as a text label with the value, regardless of its type. An example of this is shown with the code snippet and result section below.

results={
    {name="Score", type='int'},
    {name="Pass", type='bool'}},
thresholds={}
../_images/overview-result-without-thresh.png

A result can be omitted from the user interface (but still available for dependent tools) by declaring it with ui=false:

{name="SomeResult", type="float", ui=false}

See also

  • Localization for readable names or translations for parameters, thresholds and results in the user interface

  • Result types for the available result types

  • Threshold types for the available threshold types

MyTool.execute

The execute() method implements the core functionality of a tool. It is called for each instance of the tool every time a new image acquired or a parameter is changed.

The execute method should perform the tool evaluation using the configured parameters. It should communicate results and, if meaningful, add overlay graphics.

A tool must have an execute()-method. A template implementation is added automatically if the Tool generator is used.

The execute method should have this signature:

function _MyTool:execute(input, output)
  • input contains the image and other parameters needed for the execution of the plug-in tool

  • output is where the results of the plug-in tool are added.

Besides using the Nova interfaces input and output, tools are implemented using the SICK AppSpace CROWN API. This means that most CROWNs available in the used firmware of the device can be used within the execute method.

For details on the SICK AppSpace CROWN API, see the documentation included in firmware releases on https://support.sick.com/

The subset of Crowns that are used in the Nova interface itself are documented here: AppSpace Crown API.

Accessing provider data components

Different devices can produce different kinds of data, for instance height data, intensity data, or color (RGB) data.

To access different data components, first add the following line at the top of “MyTool.lua” to include the Nova.Types module:

local Types = require("Nova.Types")

The example below shows how to check if a component is present and then access it from the execute method:

if not input:hasProviderComponent(Types.DataComponent.Intensity) then
  output.log.warning("ErrorIncorrectTypeImage")
  return setResults()
end
local intensityImage = input:getProviderData(Types.DataComponent.Intensity)

It is recommended to check for existing data components and directly fail the tool if no usable component is available.

See Nova.Types.DataComponent for a full list of components available for different Nova products. Note that for some products it is possible to enable/disable components in the Acquisition settings.

Currently all calls to input:getProviderData will return an Image.

For backwards compatibility, input:getImage can be called to get the intensity data component.

SampleBlobCounter execute

Below is a walkthrough of _SampleBlobCounter.execute(). Please see the code for full details and context.

  1. Extract the presentation interface:

    local presentation = output:getPresentation()
    
  2. Extract the image from input

    local image = input:getImage()
    
  3. Declare initial values for the results

    local numBlobs = 0
    local pass = false
    
  4. Create a local function setResults() for setting the results to output

    local function setResults()
        output:add("NumBlobs", numBlobs)
        output:add("Pass", pass)
    end
    
  1. Get the region (possibly transformed by the parent tool) from input. Abort execution if the net region is empty

    local region, onlyNegative = input:getTransformedRegion()
    presentation:addRegion(region)
    if onlyNegative then
      return setResults()
    end
    
  2. Perform tool specific image processing

    local roiPixelRegion = region:toPixelRegion(image)
    -- ...
    
  3. Add objects to be overlaid in the viewer to the presentation interface

    presentation:add(blobs)
    presentation:add(cogs)
    
  4. Set results locally, using thresholds to determine if the result is successful

    numBlobs = #blobs
    pass = self.thresholds.NumBlobs:contains(numBlobs)
    
  5. Add local results to output

    setResults()
    
Presentation

Tools can add overlays to the viewer using the presentation interface. The presentation interface is retrieved from output as in step 1 in SampleBlobCounter execute.

The overlays can be text, regions or shapes. The tool region (if any) should be added to allow editing it, as in step 5 in SampleBlobCounter execute.

For the list of functions and a usage example, see the reference documentation for the presentation interface.

Use of self for tool instances

Lua provides object-orientation support much like for example C++ or Java. Tool instances can therefore be viewed as different instances of the same tool class with associated variables and functions (members). The keyword in Lua for accessing members associated with an instance is self. self is a Lua-table and members can be accessed accordingly.

To begin with, all entries provided in the tool registration gets associated with each instance.

For example:

local _MyTool = Tool.register{
    name ="MyTool",
--  ...
    parameters={
    {name="MyBool", type='bool', default=true}
    },
--  ...
}

local _MyTool:execute(input, output)
    print(self.name) -- "MyTool"
end

Parameters can be accessed similarly:

local localBool = self.parameters.MyBool
-- localBool now holds the current value of the parameter "MyBool" for the particular instance.

Constructor and destructor

A constructor and a destructor method can be defined for a tool. The constructor is called automatically each time a new instance of the tool is created. The destructor is called automatically each time an instance is destroyed. Instances are created and destroyed both when the tool is added or removed by a user as well as during job switch or configuration import.

The constructor is useful if there are instance parameters that should not be shown in the user interface, that should not be saved to the configuration and that should persist across executions of the tool. The destructor is useful if there are instance parameters that require additional actions on destruction (e.g. explicit disposal/tear-down).

The constructor and destructor have the following signatures:

function _MyTool:constructor()
function _MyTool:destructor()

All members associated with the instance in Tool.register{} are available in the constructor and the destructor.

Differences between 2D and 3D

The example shown in SampleBlobCounter execute is for a 2D tool. When making a 3D tool there are a few things that should be considered:

Building and deploying a plug-in tool

Plug-in tools should be packaged as sapk-files for deployment with SICK AppManager.

  1. In SICK AppStudio, package your plug-ins in an .sapk

  2. In SICK AppManager, remove all old apps from the device

  3. Deploy SICK Nova (.sapk) to the device

  4. Deploy your plug-in tools from step 1 to the device

  5. Restart all apps

Your plug-in tools should now be visible and usable.